﻿//
// Copyright (c) 2011 Western Data Solutions (www.westerndatasolutions.com) (Armen Shimoon)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without 
// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
// the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.Text;
using WDS.Bencode.Parsing;

namespace WDS.Bencode
{
    /// <summary>
    /// Writes bencoded document to a stream
    /// </summary>
    public class StreamBencodeGenerator
    {

        private Stream _st;
        private StreamWriter _sw;
        private readonly BencodeElement _rootElement;

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="rootElement">Root bencoded element</param>
        public StreamBencodeGenerator(BencodeElement rootElement)
        {
            _rootElement = rootElement;
        }

        /// <summary>
        /// Wrapper for greacefully closing writer
        /// </summary>
        protected void TryCloseAndDisposeWriter()
        {
            if (_sw != null)
            {
                _sw.Close();
                _sw.Dispose();
                _sw = null;
            }
        }

        /// <summary>
        /// Wrapper for gracefully creating a stream writer
        /// </summary>
        protected void CreateNewStreamWriter()
        {

            if (_st == null)
            {
                throw new BencodeGenerationException(
                    "Input stream was null.");
            }
      
            if (_st.CanSeek) _st.Seek(0, SeekOrigin.Begin);
            if (!_st.CanWrite)
            {
                throw new BencodeGenerationException(
                    "Output stream not writable.");
            }
            _sw = new StreamWriter(_st, Encoding.ASCII);

            if (_sw == null)
            {
                throw new BencodeGenerationException(
                    "Stream writer is null after creation.");
            }
        }

        /// <summary>
        /// Main entry point for encoding the document to the stream
        /// </summary>
        /// <param name="st"></param>
        public void Encode(Stream st)
        {
            try
            {
                _st = st;

                CreateNewStreamWriter();

                Generate(_rootElement);

                TryCloseAndDisposeWriter();
            }
            catch(BencodeGenerationException)
            {
                TryCloseAndDisposeWriter();
                throw;
            }
            catch (Exception ex)
            {
                TryCloseAndDisposeWriter();
                throw new BencodeGenerationException("Unexpected exception while generating: " + ex.Message, ex, st.Position);
            }
           
        }

        /// <summary>
        /// Used for encoding the given bencoded element into its string representation in the output stream
        /// </summary>
        /// <param name="element"></param>
        private void Generate(BencodeElement element)
        {

            var elementType = element.GetType();

            if (typeof(BencodeDictionary).IsAssignableFrom(elementType))
            {
                GenerateDictionary((BencodeDictionary) element);
            }

            else if (typeof(BencodeInteger).IsAssignableFrom(elementType))
            {
                GenerateInteger((BencodeInteger) element);
            }

            else if (typeof(BencodeList).IsAssignableFrom(elementType))
            {
                GenerateList((BencodeList) element);
            }

            else if (typeof(BencodeString).IsAssignableFrom(elementType))
            {
                GenerateString((BencodeString) element);
            }
            else
            {
                throw new BencodeParseException("Encountered unexpected element type of " + elementType.Name);
            }

         
        }

        /// <summary>
        /// Writes a bencoded dictionary to the output stream
        /// </summary>
        /// <param name="dictionary">The bencoded dictionary element</param>
        public void GenerateDictionary(BencodeDictionary dictionary)
        {

            try
            {
                _sw.Write("d");
                foreach (var kvp in dictionary.Elements)
                {
                    Generate(kvp.Key);
                    Generate(kvp.Value);
                }
                _sw.Write("e");
            }
            catch (BencodeGenerationException)
            {
                throw;
            }
            catch(Exception ex)
            {
                throw new BencodeGenerationException(
                    "Unexpected excepthion while generating dictionary: " + ex.Message, ex, _st.Position);
            }
              
        }

        /// <summary>
        /// Writes a bencoded list to the output stream
        /// </summary>
        /// <param name="list">The bencoded list element</param>
        public void GenerateList(BencodeList list)
        {
            try
            {
                _sw.Write("l");
                foreach (var item in list.Elements)
                {
                    Generate(item);
                }
                _sw.Write("e");
            }
            catch (BencodeGenerationException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new BencodeGenerationException(
                    "Unexpected excepthion while generating list: " + ex.Message, ex, _st.Position);
            }
        }

        /// <summary>
        /// Writes a bencoded string to the output stream
        /// </summary>
        /// <param name="str">The bencoded string element</param>
        public void GenerateString(BencodeString str)
        {

            try
            {
                _sw.Write(str.Value.Length.ToString());
                _sw.Write(":");
                _sw.Write(str.Value);

            }
            catch (BencodeGenerationException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new BencodeGenerationException(
                    "Unexpected excepthion while generating string: " + ex.Message, ex, _st.Position);
            }
        }

        /// <summary>
        /// Writes a bencoded integer to the output stream
        /// </summary>
        /// <param name="number">The bencoded integer element</param>
        public void GenerateInteger(BencodeInteger number)
        {
            try
            {
                _sw.Write("i");
                _sw.Write(number.Value.ToString());
                _sw.Write("e");
              //  throw new Exception("fake exp");
            }
            catch (BencodeGenerationException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new BencodeGenerationException(
                    "Unexpected excepthion while generating integer: " + ex.Message, null, _st.Position);
            }
        }
    }
}
